####################################
# SCons build system for Pedigree
## Tyler Kennedy (AKA Linuxhq AKA TkTech)
####################################

import os.path, tempfile, shutil
Import(['env'])

driverdir = "#" + env["PEDIGREE_BUILD_DRIVERS"]
moduledir = "#" + env["PEDIGREE_BUILD_MODULES"]
imagesdir = env["PEDIGREE_IMAGES_DIR"]
objname = driverdir + '/vmware-gfx.o'

# Common drivers - order is important
driver_common_subdirs = [
    # DMA
    'dma',

    # Pedigree-specific disk I/O
    'ata',
    'partition',

    # Pedigree-specific video
    'nvidia',

    # Pedigree-specific NICs
    '3c90x',
    # 'rtl8139',
    'loopback',

    # Pedigree-specific SCSI layer
    'scsi',

    # Pedigree-specific HID layer
    'hid',

    # Pedigree-specific USB drivers
    'usb-hcd',
    'usb-hub',
    'usb-mass-storage',
    'usb-hid',

    # Generic FTDI driver (USB serial)
    'ftdi',

    # DM9601 driver (USB ethernet)
    'dm9601',

    # CDI framework (NOT the drivers)
    'cdi',
]

# CDI drivers
cdi_drivers = [
    # CDI NIC drivers
    'e1000',
    'pcnet',
    'rtl8139',
    'sis900',

    # CDI Disk drivers
    # 'floppy' # Needs millisecond granularity in cdi_sleep_ms...
]

# Architecture-specific drivers
if env['ARCH_TARGET'] == 'X86' or env['ARCH_TARGET'] == 'X64':
    driver_arch = 'x86'
    driver_arch_subdirs = [
        'pci',
        'ne2k',
        # 'ps2mouse',
        'vbe',
        'vmware-gfx'
    ]
elif env['ARCH_TARGET'] == 'PPC':
    driver_arch = 'ppc'
    driver_arch_subdirs = [
        'ata-specific',
        'framebuffer',
        'test'
    ]
elif env['ARCH_TARGET'] == 'ARM':
    driver_arch = 'arm'
    driver_arch_subdirs = [
        'usb-glue',
    ]

    # Filter out useless drivers for ARM
    driver_common_subdirs = filter(lambda x: x not in ['ata', 'dma', 'cdi', 'nvidia', '3c90x'], driver_common_subdirs)
    cdi_drivers = []

env['cdi_driver_list'] = cdi_drivers

# Modules - order is important
module_subdirs = [
    'vfs',
    'ext2',
    'fat',
    'iso9660',
    'network-stack',
    'dhcpclient',
    'console',
    'TUI',
    'linker',
    'users',
    #'ramfs',
    'rawfs',
    'lodisk',
    'usb',
    'splash',
    'config',
    'status_server',
    'gfx-deps',
    'preload'
]

if env['installer']:
    module_subdirs += ['installer']
else:
    module_subdirs += ['init']

if env['ARCH_TARGET'] == 'ARM':
    module_subdirs = filter(lambda x: x not in ['splash', 'TUI'], module_subdirs)

# No difference yet, load all modules
initrd_modules = module_subdirs

drivers = ['drivers/' + driver_arch + '/' + i for i in driver_arch_subdirs]
drivers += ['drivers/common/' + i for i in driver_common_subdirs]
modules = ['system/' + i for i in module_subdirs]

all_modules = drivers + modules

# Custom CFLAGS for specific modules
custom_cflags = {
    'config' : ['-DSQLITE_OS_OTHER=1',
                '-DSQLITE_THREADSAFE=0',
                '-DSQLITE_TEMP_STORE=3',
                '-DSQLITE_DISABLE_LFS=1',
                '-DSQLITE_OMIT_LOCALTIME=1',
                '-DSQLITE_OMIT_FLOATING_POINT=1']
}

# Custom CXXFLAGS for specific modules
custom_cxxflags = {
}

# Flags from CXXFLAGS and CFLAGS to *remove* for a module
remove_flags = {
    'config' : ['-ffreestanding', '-fno-builtin']
}

# Try and follow this subdirectory scheme for platform code in modules to
# simplify the custom_subdirs dictionary
arch_subdir = ''
if env['ARCH_TARGET'] == 'X86':
    arch_subdir = 'i686'
elif env['ARCH_TARGET'] == 'X64':
    arch_subdir = 'amd64'
elif env['ARCH_TARGET'] == 'ARM':
    arch_subdir = 'arm'
elif env['ARCH_TARGET'] == 'PPC':
    arch_subdir = 'ppc'
else:
    arch_subdir = 'unknown'

def dma_fix(s):
    if s == 'X86' or s == 'X64':
        return 'X86'
    else:
        return s

# Custom subdirectories for specific modules
custom_subdirs = {
    'dma'           : [dma_fix(env['ARCH_TARGET']).lower()],
    'linker'        : [arch_subdir]
}

# Custom files to add to the file list
custom_files = {
    'linker'        : [arch_subdir + '/asm-' + arch_subdir + '.s']
}

# Custom include paths for specific modules
custom_incpath = {
    'cdi'           : ['#/src/modules/drivers/common/cdi/include',
                       '#/src/modules/drivers/cdi/include'],
    'status_server' : ['#/src/system/kernel'],
    'TUI'           : ['#/src/system/kernel'],
    'init'          : ['#/src/system/kernel', '#/src'],
    'splash'        : ['#/src/system/kernel'], # For nogfx
    'linker'        : ['#/src/modules/system/linker'],
    'usb-glue'      : ['#/src/system/kernel/machine/arm_beagle'] # TODO: handle other machine types
}

# Custom library paths for specific modules
custom_libs = {
}

# Default libraries for module builds
if 'STATIC_DRIVERS' in env['CPPDEFINES']:
    default_libs = []
else:
    if env['ARCH_TARGET'] == 'X64':
        default_libs = ['gcc', 'kernelrtti-nopic']
    else:
        default_libs = ['gcc', 'kernelrtti']

# Default library paths
default_libpath = [
    imagesdir + '/libraries',
    env['LIBGCC']
]

# Default include paths
default_incpath = [
    '#/src/modules',
    '#/src/system/include',
    '#/src/modules/system',
    '#/src/modules/drivers/common',
]

tmp = env.Clone()
tmp['LIBS'] = default_libs
tmp['LIBPATH'] = default_libpath
tmp['CPPPATH'] = default_incpath

def buildModule(env, path, outputBase, targetAlias):
    e = env.Clone()

    # Handle extra include directories
    base = path
    i = os.path.basename(path)
    if(os.path.exists(base + '/include')):
        e['CPPPATH'] += [base + '/include']
    if(custom_incpath.has_key(i)):
        e['CPPPATH'] += custom_incpath[i]
    if(custom_libs.has_key(i)):
        e['LIBS'] += custom_libs[i]
    if(custom_cflags.has_key(i)):
        e['CFLAGS'] += ' ' + ' '.join(custom_cflags[i])
    if(custom_cxxflags.has_key(i)):
        e['CXXFLAGS'] += ' ' + ' '.join(custom_cxxflags[i])
    if(remove_flags.has_key(i)):
        for flag in remove_flags[i]:
            e['CFLAGS'] = e['CFLAGS'].replace(flag, ' ')
            e['CXXFLAGS'] = e['CXXFLAGS'].replace(flag, ' ')
    
    output = outputBase + '/' + i + '.o'
    files = [Glob(base + '/*.c'), Glob(base + '/*.cc')]
    if custom_subdirs.has_key(i):
        for d in custom_subdirs[i]:
            files += [Glob(base + '/' + d + '/*.c'), Glob(base + '/' + d + '/*.cc')]
    if custom_files.has_key(i):
        for f in custom_files[i]:
            files += [base + '/' + f]
    
    if "STATIC_DRIVERS" in env['CPPDEFINES']:
        e.Program(output, files, LINKFLAGS='-nostdlib -Wl,-r -T $LSCRIPT', LSCRIPT = File("#src/modules/link_static.ld"))
    else:
        e.Program(output, files, LINKFLAGS='-nostdlib -Wl,-r -T $LSCRIPT', LSCRIPT = File("#src/modules/link.ld"))
    
    env.Alias(targetAlias, output)

for i in modules:
    buildModule(tmp, i, moduledir, 'modules')

for i in drivers:
    buildModule(tmp, i, driverdir, 'drivers')

# Special handling for CDI drivers
tmp['CPPPATH'] += ['#/src/modules/drivers/common/cdi/include',
                   '#/src/modules/drivers/cdi/include']

for i in cdi_drivers:
    oldpath = tmp['CPPPATH']
    
    # Patch up the path and add any include directory to the path
    i = 'drivers/cdi/' + i
    incdir = env.Dir('#/src/modules/' + i + '/include')
    if(os.path.exists(incdir.abspath)):
        tmp['CPPPATH'] += [incdir]
    buildModule(tmp, i, driverdir, 'drivers')
    
    tmp['CPPPATH'] = oldpath

# SConscript([os.path.join(i, 'SConscript') for i in drivers],exports = ['env'])
# SConscript(['drivers/SConscript-cdi'],exports = ['env'])
# SConscript([os.path.join(i, 'SConscript') for i in modules],exports = ['env'])

# Ready to generate the initrd...
builddir = env.Dir("#" + env["PEDIGREE_BUILD_BASE"])
initrdFile = env.File("#" + env["PEDIGREE_BUILD_BASE"] + "/initrd.tar")

# initrd lists

# Ensure graphics drivers are loaded before splash
initrdList = []
forceModuleOrder = ['config', 'pci', 'vbe', 'vmware-gfx', 'gfx-deps', 'splash']
for mod in forceModuleOrder:
    if mod in module_subdirs:
        initrdList += [builddir.abspath + '/modules/' + mod + '.o']
        module_subdirs.remove(mod)
    elif (mod in driver_arch_subdirs) or (mod in driver_common_subdirs) or (mod in cdi_drivers):
        initrdList += [builddir.abspath + '/drivers/' + mod + '.o']
        
        # Can't think of a more elegant way to do this at 11 PM at night. - Matt
        if mod in driver_arch_subdirs:
            driver_arch_subdirs.remove(mod)
        elif mod in driver_common_subdirs:
            driver_common_subdirs.remove(mod)
        elif mod in cdi_drivers:
            cdi_drivers.remove(mod)

# Add all drivers.
# TODO: Installation should detect hardware and install a custom initrd with
# 		only the necessary drivers to boot up (and then drivers should be
#		loaded on-demand or something)
initrdList += [builddir.abspath + '/drivers/' + i + '.o' for i in driver_arch_subdirs]
initrdList += [builddir.abspath + '/drivers/' + i + '.o' for i in driver_common_subdirs]
initrdList += [builddir.abspath + '/drivers/' + i + '.o' for i in cdi_drivers]

# Only some modules are actually needed for the actual boot of the system
# The rest can be loaded via some form of autoload mechanism (run in the init
# module perhaps?) or on demand via modload.
initrdList += [builddir.abspath + '/modules/' + i + '.o' for i in initrd_modules]

# Subsystems
initrdList += [builddir.abspath + '/subsystems/posix.o']
initrdList += [builddir.abspath + '/subsystems/pedigree-c.o']
if env['ARCH_TARGET'] == 'X86' or env['ARCH_TARGET'] == 'X64':
    initrdList += [builddir.abspath + '/subsystems/native.o']

env.Depends(initrdFile, "drivers")
env.Depends(initrdFile, "modules")
env.Depends(initrdFile, "subsys")
env.Alias("initrd", initrdFile)

def tarBuild(target, source, env):
    if env['verbose']:
        print '      Creating ' + os.path.basename(target[0].path)
    else:
        print '      Creating \033[32m' + os.path.basename(target[0].path) + '\033[0m'

    update_source = []

    # Copy all files to a temporary directory
    tmp = tempfile.mkdtemp()
    for i in source:
        fn = os.path.basename(i.path)

        dest = os.path.join(tmp, fn)
        shutil.copyfile(i.path, dest)

        update_source += [fn]
    
    # TODO: Use something other than os.system.
    os.system('cd ' + tmp + ' && ' + env['TAR'] + ' -czf ' + target[0].abspath + ' ' + ' '.join(update_source))

    shutil.rmtree(tmp)

if('STATIC_DRIVERS' in env['CPPDEFINES']):
    env.Depends("kernel", initrdList) # Kernel depends on all drivers/modules
    env['INITRD_LIST'] = initrdList
else:
    env.Command(initrdFile, initrdList, env.Action(tarBuild, None))

